#ifndef MLTOOL_VECTOR_H
#define MLTOOL_VECTOR_H

#include<iostream>
#include<memory>
#include<iterator>
#include<type_traits>
#include<algorithm>
#include"tags.h"

namespace math{
template<class T, class ALLOC = std::allocator<T> >
class vector
{
public:
    typedef ALLOC allocator_type;
    typedef typename ALLOC::size_type size_type;
    typedef typename ALLOC::difference_type difference_type;
    typedef T value_type;
    typedef T& reference;
    typedef const T& const_reference;
    typedef T* pointer;
    typedef const T* const_pointer;
    typedef pointer iterator;
    typedef const_pointer const_iterator;
    typedef std::reverse_iterator<iterator> reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
	typedef vector_tag category;

    //constructors
    //default constructor
    explicit vector(const allocator_type &a = allocator_type())
        :alloc_(a), size_(0), data_(nullptr){
        std::cout << "default constructor called!" << std::endl;
    }

    //non-init fill constructor
    explicit vector(size_type sz, const allocator_type &a = allocator_type())
        :alloc_(a), size_(sz), data_(nullptr)
    {
        std::cout << "non-init fill constructor called!" << std::endl;
        if(size_)
        {
            data_ = alloc_.allocate(size_);
            if(!std::has_trivial_default_constructor<T>::value)
                for(pointer p = data_; p!= data_ + size_; ++p)
                    alloc_.construct(p);
        }
    }

    //init fill constructor
    vector(size_type sz, const value_type &init, const allocator_type &a = allocator_type())
        :alloc_(a), size_(sz), data_(nullptr)
    {
        std::cout << "init fill constructor called!" << std::endl;
        if(size_)
        {
            data_ = alloc_.allocate(size_);
            std::uninitialized_fill_n(data_, size_, init);
        }
    }

    //range constructor
    template<class Iter>
    vector(const Iter& first, const Iter& last,
                    const allocator_type &a = allocator_type(),
                    typename std::enable_if<!std::is_integral<Iter>::value, void>::type ** = 0)
        :size_(last - first), data_(nullptr)
    {
        std::cout << "range constructor called!" << std::endl;
        if(size_)
        {
            data_ = alloc_.allocate(size_);
            std::uninitialized_copy(first, last, data_);
        }
    }

    //copy constructor
    vector(const vector &c)
        :alloc_(c.alloc_), size_(c.size_), data_(nullptr)
    {
        std::cout << "copy constructor called!" << std::endl;
        if(size_)
        {
            data_ = alloc_.allocate(size_);
            std::uninitialized_copy(c.begin(), c.end(), begin());
        }
    }

    //conversion
    template<class U>
    vector(const vector<U> &c)
        :alloc_(), size_(c.size()), data_(nullptr)
    {
        std::cout << "conversion called!" << std::endl;
        if(size_)
        {
            data_ = alloc_.allocate(size_);
            std::uninitialized_copy(c.begin(), c.end(), begin());
        }
    }

    //move constructor
    vector(vector &&c)
        :alloc_(c.alloc_), size_(0), data_(nullptr)
    {
        std::cout << "move constructor called!" << std::endl;
        std::swap(size_, c.size_);
        std::swap(data_, c.data_);
    }

    //assignment
    vector& operator=(const vector &rhs)
    {
        std::cout << "assignment called!" << std::endl;
        if(this != &rhs)
        {
            if(size_ == rhs.size_)
                std::copy(rhs.begin(),rhs.end(),begin());
            else
            {
                destroy_impl();
                if(rhs.size_)
                {
                    data_ = alloc_.allocate(rhs.size_);
                    std::uninitialized_copy(rhs.begin(), rhs.end(), data_);
                }
                size_ = rhs.size_;
            }
        }
        return *this;
    }

    //move assignment
    vector& operator=(vector &&rhs) noexcept
    {
        std::cout << "move assignment called!" << std::endl;
        if(this != &rhs)
        {
            destroy_impl();
            data_ = rhs.data_;
            size_ = rhs.size_;
            rhs.data_ = nullptr;
            rhs.size_ = 0;
        }
        return *this;
    }


    //destructor
    ~vector()
    {
        std::cout << "destructor called!" << std::endl;
        destroy_impl();
    }


    // +=, -=, *=, /=
    template<class U>
    vector& operator +=(const vector<U>& v)
    {
        for(size_type i = 0; i < size_; ++i)
            data_[i] += v[i];
        return *this;
    }

    template<class U>
    vector& operator -=(const vector<U>& v)
    {
        for(size_type i = 0; i < size_; ++i)
            data_[i] -= v[i];
        return *this;
    }

    template<class U>
    vector& operator *=(const vector<U>& v)
    {
        for(size_type i = 0; i < size_; ++i)
            data_[i] *= v[i];
        return *this;
    }

    template<class U>
    vector& operator /=(const vector<U>& v)
    {
        for(size_type i = 0; i < size_; ++i)
            data_[i] /= v[i];
        return *this;
    }

    vector&  operator +=(const value_type &t)
    {
        for(size_type i = 0; i < size_; ++i)
            data_[i] += t;
        return *this;
    }

    vector&  operator -=(const value_type &t)
    {
        for(size_type i = 0; i < size_; ++i)
            data_[i] -= t;
        return *this;
    }

    vector&  operator *=(const value_type &t)
    {
        for(size_type i = 0; i < size_; ++i)
            data_[i] *= t;
        return *this;
    }

    vector&  operator /=(const value_type &t)
    {
        value_type tmp = 1.0/t;
        this -> operator *=(tmp);
        return *this;
    }

	//operator - (negative)
	friend inline  vector operator -(const vector &v1)
	{
		vector ans(v1.size());
		for(size_type i = 0; i < v1.size_; ++i)
			ans.data_[i] = -v1.data_[i];
		return ans;
	}

	friend inline vector&& operator -(vector &&v)
	{
		v *= -1;
		return std::move(v);
	}

    // operator +
    friend inline vector operator +(const vector &v1, const vector &v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::cout << "1st called!" << std::endl;
        vector ans(v1.size());
        std::transform(v1.begin(), v1.end(), v2.begin(), ans.begin(), std::plus<value_type>());
        return ans;
    }

    friend inline vector&& operator +(vector &&v1, const vector &v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::cout << "2nd called!" << std::endl;
        return std::move(v1 += v2);
    }

    friend inline vector&& operator +(const vector &v1, vector &&v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::cout << "3rd called!" << std::endl;
        return std::move(v2 += v1);
    }

    friend inline vector&& operator +(vector &&v1, vector &&v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::cout << "4th called!" << std::endl;
        return std::move(v1 += v2);
    }

    // operator -
    friend inline vector operator -(const vector &v1, const vector &v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        vector ans(v1.size());
        std::transform(v1.begin(), v1.end(), v2.begin(), ans.begin(), std::minus<value_type>());
        return ans;
    }

    friend inline vector&& operator -(vector &&v1, const vector &v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        return std::move(v1 -= v2);
    }

    friend inline vector&& operator -(const vector &v1, vector &&v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::transform(v1.begin(), v1.end(), v2.begin(), v2.begin(), std::minus<value_type>());
        return std::move(v2);
    }

    friend inline vector&& operator -(vector &&v1, vector &&v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        return std::move(v1 -= v2);
    }

    // operator *
    friend inline vector operator*(const vector &v1, const vector &v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::cout << "1st called!" << std::endl;
        vector ans(v1.size());
        std::transform(v1.begin(), v1.end(), v2.begin(), ans.begin(), std::multiplies<value_type>());
        return ans;
    }

    friend inline vector&& operator*(vector &&v1, const vector &v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::cout << "2nd called!" << std::endl;
        return std::move(v1*= v2);
    }

    friend inline vector&& operator*(const vector &v1, vector &&v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::cout << "3rd called!" << std::endl;
        return std::move(v2*= v1);
    }

    friend inline vector&& operator*(vector &&v1, vector &&v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::cout << "4th called!" << std::endl;
        return std::move(v1*= v2);
    }

    // operator /
    friend inline vector operator /(const vector &v1, const vector &v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        vector ans(v1.size());
        std::transform(v1.begin(), v1.end(), v2.begin(), ans.begin(), std::divides<value_type>());
        return ans;
    }

    friend inline vector&& operator /(vector &&v1, const vector &v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        return std::move(v1 /= v2);
    }

    friend inline vector&& operator /(const vector &v1, vector &&v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        std::transform(v1.begin(), v1.end(), v2.begin(), v2.begin(), std::divides<value_type>());
        return std::move(v2);
    }

    friend inline vector&& operator /(vector &&v1, vector &&v2)
    {
#ifdef CHECK_DIMENSION_MATCH
        assert(v1.size() == v2.size());
#endif
        return std::move(v1 /= v2);
    }

	// +, -, * , / scalar

	friend inline vector operator + (const vector &v, value_type t)
	{
		vector ans(v.size());
		for(int i = 0; i < v.size(); ++i)
			ans.data_[i] = t + v.data_[i];
		return ans;
	}

	friend inline vector operator + (value_type t, const vector &v)
	{
		return v + t;
	}

	friend inline vector&& operator +(vector &&v, value_type t)
	{
		v += t;
		return std::move(v);
	}

	friend inline vector&& operator +(value_type t, vector &&v)
	{
		v += t;

		return std::move(v);

	}


	// *
	friend inline vector operator * (const vector &v, value_type t)
	{
		vector ans(v.size());
		for(int i = 0; i < v.size(); ++i)
			ans.data_[i] = v.data_[i] * t;
		return ans;
	}

	friend inline vector operator * (value_type t, const vector &v)
	{
		return v * t;
	}

	friend inline vector&& operator *(vector &&v, value_type t)
	{
		v *= t;
		return std::move(v);
	}

	friend inline vector&& operator *(value_type t, vector &&v)
	{
		v *= t;
		return std::move(v);
	}

	// -
	friend inline vector operator - (const vector &v, value_type t)
	{
		vector ans(v.size());
		for(int i = 0; i < v.size(); ++i)
			ans.data_[i] = v.data_[i] - t;
		return ans;
	}

	friend inline vector operator - (value_type t, const vector &v)
	{
		vector ans(v.size());
		for(int i = 0; i < v.size(); ++i)
			ans.data_[i] = t - v.data_[i];
		return ans;
	}

	friend inline vector&& operator -(vector &&v, value_type t)
	{
		v -= t;
		return std::move(v);
	}

	friend inline vector&& operator -(value_type t, vector &&v)
	{
		for(size_type i = 0; i < v.size(); ++i)
			v.data_[i] = t - v.data_[i];
		return std::move(v);
	}


	// /
	friend inline vector operator / (const vector &v, value_type t)
	{
		vector ans(v.size());
		for(int i = 0; i < v.size(); ++i)
			ans.data_[i] = v.data_[i] / t;
		return ans;
	}

	friend inline vector operator / (value_type t, const vector &v)
	{
		vector ans(v.size());
		for(int i = 0; i < v.size(); ++i)
			ans.data_[i] = t / v.data_[i];
		return ans;
	}

	friend inline vector&& operator /(vector &&v, value_type t)
	{
		v /= t;
		return std::move(v);
	}

	friend inline vector&& operator /(value_type t, vector &&v)
	{
		for(size_type i = 0; i < v.size(); ++i)
			v.data_[i] = t / v.data_[i];
		return std::move(v);
	}

    size_type size() const
    {
        return size_;
    }

    size_type max_size() const
    {
        return alloc_.max_size();
    }

    bool empty() const
    {
        return !data_;
    }

    reference operator[](size_type i)
    {
        return data_[i];
    }

    const_reference operator[](size_type i) const
    {
        return data_[i];
    }

    iterator begin()
    {
        return data_;
    }

    const_iterator begin() const
    {
        return data_;
    }

    pointer data()
    {
        return data_;
    }

    const_pointer data() const
    {
        return data_;
    }

    iterator end()
    {
        return data_ + size_;
    }

    const_iterator end() const
    {
        return data_ + size_;
    }

    reverse_iterator rbegin()
    {
        return reverse_iterator(end());
    }

    const_reverse_iterator rbegin() const
    {
        return const_reverse_iterator(end());
    }

    reverse_iterator rend()
    {
        return reverse_iterator(begin());
    }

    const_reverse_iterator rend() const
    {
        return const_reverse_iterator(begin());
    }

    //swap
    void swap(vector &tmp)
    {
        std::cout << "swap called!" << std::endl;
        if(this != &tmp)
        {
            std::swap(size_, tmp.size_);
            std::swap(data_, tmp.data_);
        }
    }

    friend void swap(vector &a1, vector &a2)
    {
        a1.swap(a2);
    }

    //resize
    void resize(size_type sz, bool preserve = false)
    {
        resize_impl(sz,preserve);
    }

protected:
    ALLOC alloc_;
    pointer data_;
    size_type size_;

    inline void destroy_impl()
    {
        if(size_)
        {
            if(!std::is_trivially_destructible<T>::value)
                for(pointer p = data_; p != data_ + size_; ++p)
                    alloc_.destroy(p);
            alloc_.deallocate(data_, size_);
        }
    }

    void resize_impl(size_type sz_new, bool preserve)
    {
        if(size_ != sz_new)
        {
            pointer data_old = data_;
            data_ = nullptr;
            size_type sz_old = size_;
            size_ = sz_new;
            if(sz_new)
            {
                data_ = alloc_.allocate(sz_new);
                if(preserve)
                {
                    pointer si = data_old; //source
                    pointer di = data_;  //destination
                    if(sz_new < sz_old)
                    {
                        for(; di != data_ + sz_new; ++di)
                        {
                            alloc_.construct(di, *si);
                            ++si;
                        }
                    }
                    else  //sz_new > sz_old
                    {
                        for(; di != data_ + sz_old; ++di)
                        {
                            alloc_.construct(di, *si);
                            ++si;
                        }
                        value_type init = value_type();
                        for(; di != data_ + sz_new; ++di)
                            alloc_.construct(di, init);
                    }
                }
                else //preserve == false
                    if(!std::has_trivial_default_constructor<T>::value)
                        for(pointer di = data_; di != data_ + sz_new; ++di)
                            alloc_.construct(di);
            }

            if(sz_old)
            {
                if(!std::is_trivially_destructible<T>::value)
                    for(pointer p = data_old; p != data_old + sz_old; ++p)
                        alloc_.destroy(p);
                alloc_.deallocate(data_old, sz_old);
            }

        }
    }

};

} //namespace math
#endif
